Bind SCP file timestamps to open descriptor#1037
Open
yosuke-wolfssl wants to merge 1 commit into
Open
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Hardens the SCP receive path against a symlink TOCTOU by applying peer-supplied timestamps to the still-open destination file descriptor (when supported), instead of applying them later via a path-based utimes().
Changes:
- Update SCP receive timestamp application to prefer fd-based timestamp setting (
futimes/_futime) and flush before applying times to prevent close-time writes from clobberingmtime. - Add
WFUTIMESport macro gated byHAVE_FUTIMES, and detectfutimesinconfigure.ac. - Add an end-to-end unit test asserting received files end up with the peer-supplied
mtime/atime.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/wolfscp.c |
Applies timestamps via the open file descriptor (when available) with pre-flush ordering to prevent TOCTOU and mtime clobbering. |
wolfssh/port.h |
Introduces WFUTIMES macro gated by HAVE_FUTIMES for portability. |
configure.ac |
Adds futimes feature detection to enable fd-based timestamp updates. |
tests/unit.c |
Adds an end-to-end regression test for SCP receive timestamp behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
f939cc7 to
75b4dc1
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1037
Scan targets checked: wolfssh-bugs, wolfssh-src
No new issues found in the changed files. ✅
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bind SCP file timestamps to the open descriptor (fix symlink TOCTOU)
Summary
In the SCP receive path, peer-supplied modification/access times were applied with a path-based
utimes()after the destination file was closed. On POSIX,utimes()follows symlinks, so a local process running as the authenticated user could replace the just-written file with a symlink in the window between the close and the timestamp update, redirecting the peer-controlledmTime/aTimeonto an arbitrary target inode.This change binds the timestamp update to the open file descriptor so it can no longer be redirected through the path.
Changes
src/wolfscp.cSetTimestampInfo()now takes the openWFILE*. On descriptor-capable platforms it applies the timestamps to the open file:futimes(fileno(fp), ...)(via the newWFUTIMESport macro)_futime(_fileno(fp), ...)instead of re-opening the pathfclose()does not write and overwrite the modification time.WOLFSSH_SCP_FILE_DONEhandler now applies the timestamp beforeWFCLOSEon descriptor-capable platforms (WOLFSSH_SCP_FD_UTIMES), and keeps the original after-close path-based update on platforms without a descriptor interface — so no port loses its timestamp to the closing flush.fp, matching the POSIX contract.futimes/_futime/utimes) are normalized toWS_FATAL_ERRORon failure, consistent with the function's documentedWS_SUCCESS/negative contract.wolfssh/port.hWFUTIMES(fd, t)macro, defined only whenHAVE_FUTIMESis detected.futimes()is a BSD extension (not POSIX), so gating it avoids implicit-declaration breakage on targets that lack it (Solaris, older Android, strict-standard builds); those fall back to the existing path-basedWUTIMES.configure.acAC_CHECK_FUNCS([... futimes])soHAVE_FUTIMESis detected per platform.tests/unit.ctest_ScpRecvCallback_Timestamp, an end-to-end regression test that drives the default receive callback through a full single-file receive and asserts the written file'smtime/atimeequal the peer-supplied values.When the descriptor path is compiled in, it also pins the flush-before-set ordering. Lives in the internal-symbol unit test (
libwolfssh_test).Platform behavior
futimes(HAVE_FUTIMES)futimes()on the open fd, before close_futime()on the open CRT fd, before closefutimesutimes()after closeWUTIMESafter close (unchanged)Testing
tests/unit.test— newScpRecvCallback_Timestamppasses; full suite green under ASan + UBSan (no leak detection on macOS).fcloseclobbers the modification time), confirming it catches the regression; restoring the fix makes it pass.HAVE_FUTIMESforced off, the build is clean and the path-based after-close update still applies the timestamp correctly.scp -pinto the wolfSSH echoserver sink — the destinationmtimematches the source; before the fix it was the current time.scripts/scp.test— all cases pass.--enable-scp, no sanitizers) — clean, no new warnings.